﻿package de.gameduell.framework.modules
{
	import de.gameduell.framework.debug.Debug;
	import de.gameduell.framework.modules.event.DisableEvent;
	import de.gameduell.framework.threads.IThread;
	import de.gameduell.framework.queue.BlockingCallbackAction
	import de.gameduell.framework.threads.IThread;
	
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	
	/**
	 * update timer or delay for modules, 
	 * with lost-frame-compensation, foces fixed framerate (timebased)
	 * 
	 * @ftru
	 **/
	public class ModuleHeartbeat implements IThread
	{
		protected static const maxFramesPerHeartbeat:int = 100;
		
		protected var module:IModule;
		
		protected var isBlockingTimer:Boolean;
		protected var fixedFps:Number;
		protected var timeTolLiveInMillis:int;
		protected var updateWhenTimeToLiveIsSet:Boolean;

		protected var updateTimer:Timer;
		protected var disableTimer:Timer;
		protected var startedAtTime:int;
		protected var frameIndex:int;
		
		public function ModuleHeartbeat(module:IModule) {
			this.module = module;
		}
		
		public function startTimer(fixedFps:Number = 30, timeTolLiveInMillis:int = 0, updateWhenTimeToLiveIsSet:Boolean = true, isBlockingTimer:Boolean = false):void {
			Debug.trace("ModuleHeartbeat::startTimer() module=" + module);
			
			this.fixedFps = fixedFps;
			this.timeTolLiveInMillis = timeTolLiveInMillis;
			this.updateWhenTimeToLiveIsSet = updateWhenTimeToLiveIsSet;
			this.isBlockingTimer = isBlockingTimer;
			this.startedAtTime = new Date().time;
			this.frameIndex = 0;
			
			// disable timer
			if (timeTolLiveInMillis > 0) {
				disableTimer = new Timer(timeTolLiveInMillis, 1);
				disableTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onEndOfLife);
				disableTimer.start();
			}

			// update timer
			if (timeTolLiveInMillis == 0 || updateWhenTimeToLiveIsSet) {
				updateTimer = new Timer(1000.0 / fixedFps, 0);
				updateTimer.addEventListener(TimerEvent.TIMER, onHeartbeat);
				updateTimer.start();
			}
			
			// start block if required
			if (isBlockingTimer) {
				new BlockingCallbackAction(this, 1000.0 / fixedFps, true);
			}
						
		}
		
		public function stopTimer():void{
			Debug.trace("ModuleHeartbeat::stopTimer() module=" + module);
			
			if (disableTimer != null) {
				disableTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, onEndOfLife);
				disableTimer.stop();
				disableTimer = null;
			}
			
			if (updateTimer != null) {
				updateTimer.removeEventListener(TimerEvent.TIMER, onHeartbeat);
				updateTimer.stop();
				updateTimer = null;
			}
			
		}
		
		public function isTimerRunning():Boolean {
			return updateTimer != null || disableTimer != null;
		}		

		protected function onEndOfLife(event:Event):void {
			
			// stop timer, remove resources
			stopTimer();
			
			// disable module
			module.handleEvent(new DisableEvent());
		}

		protected function onHeartbeat(event:Event):void {
			
			// lost frame compensation
			var timeSinceStart:Number = new Date().time - startedAtTime;
			var targetFrame:int = fixedFps * timeSinceStart;
			var subFrames:int = targetFrame - frameIndex;
			 
			// fixed frame rate update, avoid too heavy impacts (maxFramesPerHeartbeat)
			subFrames = Math.min(subFrames, maxFramesPerHeartbeat); 
			var dt:Number = Number(subFrames) * fixedFps;

			// update state			
			module.timerUpdate(subFrames, dt);

			// count take frames			
			frameIndex += subFrames;
		}

		/* INTERFACE de.gameduell.framework.threads.IBlockingThread */

		// block the action queue while running?
		public function run():Boolean {
			return isBlockingTimer && isTimerRunning();
		}
		
		public function onStartExecution():void {
		}
		
		public function onFinishExecution():void {
		}
		
		public function get isBlockingThread():Boolean {
			return isBlockingTimer;
		}
		
		public function get description():String {
			return "ModuleHeartbeat." + this;
		}

		
	}
}